﻿using System;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.ServiceModel.Activation;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Client;
using System.Text.RegularExpressions;
using Microsoft.SharePoint.Taxonomy;
using Microsoft.SharePoint.WebControls;
using System.Reflection;
using System.Xml;
using System.IO;
using System.Web;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using Sukul.SharePoint.Common;

namespace Sukul.SharePoint.Taxomatic.Service
{
    /// <summary>
    /// The class contains all the taxonomy logic. It will access via its singleton property: TaxonomyHelper.Instance.Something
    /// </summary>
    public class TaxonomyHelper
    {
        int iTermCount = 0;
        const string DELIMITER = "";

        // Do not allow this class to be instantiated
        public TaxonomyHelper()
        { }

        #region [Singleton]
        private static TaxonomyHelper _instance = null;
        private static object _lockObj = "none";
        public static TaxonomyHelper Instance
        {
            get
            {
                lock (_lockObj)
                {
                    if (_instance == null)
                    {
                        _instance = new TaxonomyHelper();
                    }
                }
                return _instance;
            }
        }
        #endregion

        #region [Public Methods]
        public void SyncHiddenList(string siteUrl)
        {
            using (SPSite site = new SPSite(siteUrl))
            {
                TaxonomySession.SyncHiddenList(site);
                site.Dispose();
            }
        }

        public DTO.Term GetSingleTerm(string termId)
        {
            DTO.Term myTerm = null;
            SPSite mySite = SPContext.Current.Site;
            TaxonomySession session = new TaxonomySession(mySite);
            Term term = session.GetTerm(new Guid(termId));
            myTerm = ConvertToDTO(term, 0);

            return myTerm;
        }

        public DTO.TermStores GetTermTree()
        {
            DTO.TermStores termStores = new DTO.TermStores();
            DTO.TermStore termStore = null;
            DTO.TaxonomyGroup termGroup = null;
            DTO.Termset termSet = null;

            SPSite mySite = SPContext.Current.Site;

            TaxonomySession session = new TaxonomySession(mySite);

            // Get all the TermStores associated with the provided site.
            TermStoreCollection stores = session.TermStores;
            //TermStore store = session.DefaultSiteCollectionTermStore;

            foreach (var store in stores)
            {
                if (store.Name.Equals("System")) { continue; }
                termStore = new DTO.TermStore();
                termStore.Id = store.Id;
                termStore.Name = store.Name;
                termStore.WorkingLanguage = store.WorkingLanguage;
                foreach (var language in store.Languages)
                {
                    termStore.Languages.Add(language);
                }

                foreach (var group in store.Groups)
                {
                    termGroup = new DTO.TaxonomyGroup();
                    termGroup.Id = group.Id;
                    termGroup.Name = group.Name;
                    termGroup.CreatedDate = group.CreatedDate;
                    termGroup.Description = group.Description;

                    foreach (var termset in group.TermSets)
                    {
                        termSet = new DTO.Termset();
                        termSet.Id = termset.Id;
                        termSet.Name = termset.Name;
                        termSet.Description = termset.Description;
                        termSet.CreatedDate = termset.CreatedDate;
                        termSet.IsAvailableForTagging = termset.IsAvailableForTagging;
                        termSet.IsOpenForTermCreation = termset.IsOpenForTermCreation;
                        termSet.LastModifiedDate = termset.LastModifiedDate;

                        foreach (Term topTerm in termset.Terms)
                        {
                            termSet.Terms = TraverseTermSet(termset.Terms);
                        }
                        termGroup.Termsets.Add(termSet);
                    }
                    termStore.TaxonomyGroups.Add(termGroup);
                }
                termStores.Add(termStore);
            }

            return termStores;
        }

        public string ExportTaxonomy()
        {
            DTO.TermStores tree = GetTermTree();
            return ServiceBase.Instance.XMLSerialize(tree);
        }

        public void ConvertTaxonomy(string xmlFileName, string csvFile)
        {
            DTO.TermStores stores = null;

            using (StreamReader sr = new StreamReader(xmlFileName))
            {
                stores = ServiceBase.Instance.XMLDeSerialize<DTO.TermStores>(sr.ReadToEnd());
            }

            using (var writer = new StreamWriter(csvFile, false))
            {
                writer.WriteLine("Id, Taxonomy Store, Group, Termset, Top Level Term");
                foreach (var store in stores)
                {
                    writer.WriteLine(string.Format("\"{0}\",\"{1}\"", store.Id, store.Name));
                    foreach (var group in store.TaxonomyGroups)
                    {
                        writer.WriteLine(string.Format("\"{0}\",,\"{1}\"", group.Id, group.Name));
                        foreach (var termset in group.Termsets)
                        {
                            writer.WriteLine(string.Format("\"{0}\",,,\"{1}\"", termset.Id, termset.Name));
                            foreach (var term in termset.Terms)
                            {
                                writer.WriteLine(string.Format("\"{0}\",,,,\"{1}\"", term.Id, term.Name));
                                foreach (var childTerm in term.Terms)
                                {
                                    WriteTerm(writer, childTerm, 5);
                                }
                            }
                        }
                    }
                }
                writer.Close();
            }

            return;
        }

        private void WriteTerm(StreamWriter writer, DTO.Term term, int indentLevel)
        {
            writer.WriteLine(string.Format("\"{0}\"{1}\"{2}\"", term.Id, new String(',', indentLevel), term.Name));
            indentLevel++;
            foreach (var childTerm in term.Terms)
            {
                WriteTerm(writer, childTerm, indentLevel);
            }
            return;
        }

        public bool ImportTaxonomy(string xmlFileName, ref List<string> log, ref List<string> errorLog)
        {
            log = new List<string>();
            errorLog = new List<string>();
            bool isOk = true;

            int iTermsetCount = 0;
            int iTermTopCount = 0;

            DTO.TermStores stores = null;
            using (StreamReader sr = new StreamReader(xmlFileName))
            {
                stores = ServiceBase.Instance.XMLDeSerialize<DTO.TermStores>(sr.ReadToEnd());
            }
            Microsoft.SharePoint.Taxonomy.TermStore locatedStore = null;
            TaxonomySession session = null;

            SPSite mySite = SPContext.Current.Site;

            try
            {
                log.Add("Starting import..." + DELIMITER);
                foreach (var store in stores)
                {
                    iTermsetCount = 0;
                    iTermTopCount = 0;
                    iTermCount = 0;
                    session = new TaxonomySession(mySite);
                    locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name || s.Id == store.Id);
                    if (null == locatedStore)
                    {
                        throw new ArgumentException(string.Format("Termstore '{0}' was not found. Please create a termstore with this name and try again.", store.Name));
                    }

                    log.Add("Clearing existing taxonomy..." + DELIMITER);
                    // Delete all Groups first
                    ClearTaxonomy(store);
                    log.Add("Existing taxonomy cleared." + DELIMITER);

                    foreach (var group in store.TaxonomyGroups)
                    {
                        try
                        {
                            if (group.Name.ToLower().Equals("system")) { continue; }

                            CreateGroup(store, group, locatedStore, log);
                            foreach (var termset in group.Termsets)
                            {
                                try
                                {
                                    iTermsetCount++;
                                    CreateTermSet(store, termset, group, locatedStore, log);
                                    foreach (var term in termset.Terms)
                                    {
                                        try
                                        {
                                            iTermTopCount++;
                                            iTermCount++;
                                            /* Create top level terms */
                                            CreateParentTerm(store, termset, term, locatedStore, log);
                                            if (null != term.Terms && term.Terms.Count > 0)
                                            {
                                                foreach (var childTerm in term.Terms)
                                                {
                                                    // Create child terms 
                                                    CreateChildTerm(term, childTerm, store, locatedStore, log, errorLog);
                                                }
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            isOk = false;
                                            errorLog.Add(ex.ToString() + DELIMITER);
                                            log.Add(ex.ToString() + DELIMITER);
                                        }
                                    }
                                }
                                catch (Exception ex)
                                {
                                    isOk = false;
                                    errorLog.Add(ex.ToString() + DELIMITER);
                                    log.Add(ex.ToString() + DELIMITER);
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            isOk = false;
                            errorLog.Add(ex.ToString() + DELIMITER);
                            log.Add(ex.ToString() + DELIMITER);
                        }
                    }
                    log.Add(string.Format("Processed termstore: '{0}' containing {1} groups, {2} termsets, {3} top level terms and a total of {4} terms." + DELIMITER,
                                    store.Name, store.TaxonomyGroups.Count, iTermsetCount, iTermTopCount, iTermCount));
                }
            }
            catch (Exception ex)
            {
                isOk = false;
                errorLog.Add(ex.ToString() + DELIMITER);
                //throw new ApplicationException("An error occured while trying to import the taxonomy.", ex);
            }

            return isOk;
        }

        #endregion

        #region [Private Methods]
        private Collection<DTO.Term> TraverseTermSet(TermCollection tc)
        {
            Collection<DTO.Term> terms = new Collection<DTO.Term>();
            DTO.Label lbl = null;

            DTO.Term term = null;
            foreach (Term t in tc)
            {
                term = new DTO.Term();
                term.Id = t.Id;
                term.Name = t.Name;
                term.IsAvailableForTagging = t.IsAvailableForTagging;
                term.IsDeprecated = t.IsDeprecated;
                term.IsKeyword = t.IsKeyword;
                term.IsReused = t.IsReused;
                term.IsRoot = t.IsRoot;
                term.IsSourceTerm = t.IsSourceTerm;
                foreach (var label in t.Labels)
                {
                    lbl = new DTO.Label();
                    lbl.Language = label.Language;
                    lbl.Value = label.Value;
                    lbl.IsDefaultForLanguage = label.IsDefaultForLanguage;
                    term.Labels.Add(lbl);
                }

                terms.Add(term);

                if (null != t.Terms)
                {
                    term.Terms = TraverseTermSet(t.Terms);
                }
            }
            return terms;
        }

        private DTO.Term ConvertToDTO(Term term, int level)
        {
            DTO.Label label = null;
            DTO.Term myTerm = new DTO.Term();
            int localeID = HarvestCurrentLCID(System.Web.HttpContext.Current);
            myTerm.Id = term.Id;
            myTerm.Name = term.Name;
            myTerm.Owner = term.Owner;


            myTerm.Description = term.GetDescription(localeID);
            myTerm.DefaultLabel = term.GetDefaultLabel(localeID);
            myTerm.LastModifiedDate = term.LastModifiedDate;
            if (level == 0)
            {
                myTerm.SourceTerm = ConvertToDTO(term.SourceTerm, 1);
            }
            myTerm.IsAvailableForTagging = term.IsAvailableForTagging;

            foreach (var lbl in term.Labels)
            {
                label = new DTO.Label();
                label.IsDefaultForLanguage = lbl.IsDefaultForLanguage;
                label.Language = lbl.Language;
                label.Value = lbl.Value;
                myTerm.Labels.Add(label);
            }

            //myTerm.Parent = ConvertToDTO(term.Parent);

            return myTerm;
        }

        private int HarvestCurrentLCID(System.Web.HttpContext context)
        {
            return (int)Microsoft.SharePoint.WebControls.SPControl.GetContextWeb(context).Language;
        }

        private void ClearTaxonomyTerm(TermStore store, Term term)
        {
            foreach (Term t in term.Terms)
            {
                ClearTaxonomyTerm(store, t);
            }
            if (null != store.GetTerm(term.Id))
            {
                term.Delete();
                store.CommitAll();
            }
        }

        private void ClearTaxonomy(DTO.TermStore store)
        {
            var session = new TaxonomySession(SPContext.Current.Site);
            var locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name);
            if (null != locatedStore)
            {
                foreach (var group in locatedStore.Groups)
                {
                    foreach (var termset in group.TermSets)
                    {
                        foreach (var term in termset.Terms)
                        {
                            if (!termset.Name.Equals("Orphaned Terms") && !termset.Name.Equals("Keywords"))
                            {
                                ClearTaxonomyTerm(locatedStore, term);
                            }
                        }
                        if (!termset.Name.Equals("Orphaned Terms") && !termset.Name.Equals("Keywords"))
                        {
                            termset.Delete();
                        }
                    }
                    if (!group.Name.Equals("System"))
                    {
                        group.Delete();
                    }
                }
                locatedStore.CommitAll();
            }
        }

        private void CreateGroup(DTO.TermStore store, DTO.TaxonomyGroup group, TermStore locatedStore, List<string> log)
        {
            //var session = new TaxonomySession(SPContext.Current.Site);
            //var locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name);
            if (null != locatedStore)
            {
                if (group.Name.Equals("System")) { return; }

                // The API does not provide a way to create Groups by ID
                var locatedGroup = locatedStore.Groups.FirstOrDefault(g => g.Name == group.Name);
                if (null == locatedGroup)
                {
                    log.Add("Creating group '" + group.Name + "'..." + DELIMITER);
                    locatedGroup = locatedStore.CreateGroup(group.Name);
                    log.Add("Group: '" + group.Name + "' created" + DELIMITER);
                }
                locatedGroup.Description = group.Description;
                locatedStore.CommitAll();
            }
        }

        private void CreateTermSet(DTO.TermStore store, DTO.Termset termset, DTO.TaxonomyGroup group, TermStore locatedStore, List<string> log)
        {
            //var session = new TaxonomySession(SPContext.Current.Site);
            //var locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name);
            if (null != locatedStore)
            {
                if (termset.Name.Equals("Keywords")) { return; }
                if (termset.Name.Equals("Orphaned Terms")) { return; }

                // The API does not provide a way to create Groups by ID
                var locatedGroup = locatedStore.Groups.FirstOrDefault(g => g.Name == group.Name);
                if (null != locatedGroup)
                {
                    var locatedTermset = locatedGroup.TermSets.FirstOrDefault(t => t.Id == termset.Id);
                    if (null == locatedTermset)
                    {
                        log.Add("Creating termset '" + termset.Name + "'..." + DELIMITER);
                        locatedTermset = locatedGroup.CreateTermSet(termset.Name, termset.Id, store.WorkingLanguage);
                        locatedTermset.Description = termset.Description;
                        locatedTermset.IsAvailableForTagging = termset.IsAvailableForTagging;
                        locatedTermset.IsOpenForTermCreation = termset.IsOpenForTermCreation;
                        locatedStore.CommitAll();
                        log.Add("Termset '" + termset.Name + "' created." + DELIMITER);
                    }
                }
            }
        }

        private void CreateParentTerm(DTO.TermStore store, DTO.Termset termset, DTO.Term term, TermStore locatedStore, List<string> log)
        {
            if (termset.Name.Equals("Orphaned Terms")) { return; }
            if (termset.Name.Equals("Keywords")) { return; }

            Term locatedTerm = null;
            //var session = new TaxonomySession(SPContext.Current.Site);
            //var locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name);
            if (null != locatedStore)
            {
                var locatedTermSet = locatedStore.GetTermSet(termset.Id);
                if (null != locatedTermSet)
                {
                    locatedTerm = locatedStore.GetTerm(term.Id);
                    if (null == locatedTerm)
                    {
                        log.Add("Creating top level term: '" + term.Name + "'..." + DELIMITER);
                        locatedTerm = locatedTermSet.CreateTerm(term.Name, store.WorkingLanguage, term.Id);
                        locatedTerm.IsAvailableForTagging = term.IsAvailableForTagging;
                        foreach (var label in term.Labels)
                        {
                            if (!label.IsDefaultForLanguage)
                            {
                                if (null == locatedTerm.Labels.FirstOrDefault(l => l.Value == label.Value))
                                {
                                    locatedTerm.CreateLabel(label.Value, label.Language, label.IsDefaultForLanguage);
                                }
                            }
                        }
                        locatedStore.CommitAll();
                        log.Add("Top level term '" + term.Name + "' created" + DELIMITER);
                    }
                    else
                    {
                        log.Add("Term '" + term.Name + "' already exists, attempting to reuse" + DELIMITER);
                        locatedTermSet.ReuseTerm(locatedTerm, true);
                        log.Add("Term '" + term.Name + "' was successfully reused" + DELIMITER);
                    }
                }
            }
        }

        private void CreateChildTerm(DTO.Term parentTerm, DTO.Term term, DTO.TermStore store, TermStore locatedStore, List<string> log, List<string> errorLog)
        {
            Term locatedParentTerm = null;
            Term locatedTerm = null;
            //var session = new TaxonomySession(SPContext.Current.Site);
            //var locatedStore = session.TermStores.FirstOrDefault(s => s.Name == store.Name);
            try
            {
                if (null != locatedStore)
                {
                    log.Add("Creating term: '" + term.Name + "'");
                    locatedParentTerm = locatedStore.GetTerm(parentTerm.Id);
                    if (null != locatedParentTerm)
                    {
                        locatedTerm = locatedStore.GetTerm(term.Id);
                        if (null == locatedTerm || !locatedTerm.IsAvailableForTagging)
                        {
                            locatedTerm = locatedParentTerm.CreateTerm(term.Name, store.WorkingLanguage, term.Id);
                            locatedTerm.IsAvailableForTagging = term.IsAvailableForTagging;
                            foreach (var label in term.Labels)
                            {
                                if (!label.IsDefaultForLanguage)
                                {
                                    if (null == locatedTerm.Labels.FirstOrDefault(l => l.Value == label.Value))
                                    {
                                        locatedTerm.CreateLabel(label.Value, label.Language, label.IsDefaultForLanguage);
                                    }
                                }
                            }
                            iTermCount++;
                            locatedStore.CommitAll();
                            log.Add("Term: '" + term.Name + "' created");
                        }
                        else if (null == locatedParentTerm.Terms.FirstOrDefault(t => t.Id == term.Id))
                        {
                            locatedParentTerm.ReuseTerm(locatedTerm, true);
                        }
                    }
                }
                if (null != term.Terms && term.Terms.Count > 0)
                {
                    foreach (var childTerm in term.Terms)
                    {
                        CreateChildTerm(term, childTerm, store, locatedStore, log, errorLog);
                    }
                }
            }
            catch (Exception ex)
            {
                errorLog.Add(ex.ToString() + DELIMITER);
            }
        }
        #endregion

    }
}
